/**
 * Copyright (C) 2001-2018 by RapidMiner and the contributors
 * 
 * Complete list of developers available at our web site:
 * 
 * http://rapidminer.com
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Affero General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License along with this program.
 * If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.tools.cipher;

import com.rapidminer.io.Base64;
//
// import sun.misc.BASE64Decoder;
// import sun.misc.BASE64Encoder;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.xml.bind.DatatypeConverter;


/**
 * This class can be used to encrypt and decrypt given strings based on a key generated by the user.
 * Please note that classes using this tool class should first ensure that a user key is available
 * by invoking the method isKeyAvailable().
 * 
 * @author Ingo Mierswa, Marco Boeck
 */
@SuppressWarnings("deprecation")
public class CipherTools {

	/** algorithm used to encrypt/decrypt */
	private static final String CIPHER_TYPE = "DESede";

	/** String used to flag new encoded passwords */
	private static final String FLAG_NEW_ENCODE = "_";

	/** the minimum length of string to encode to Base64 before 6.0.003 */
	private static final int FORMER_MINLENGTH_TO_ENCODE = 4;

	/**
	 * Returns whether an encryption {@link Key} is available or not.
	 * 
	 * @return <code>true</code> if a cipher {@link Key} is available; <code>false</code> otherwise
	 */
	public static boolean isKeyAvailable() {
		try {
			KeyGeneratorTool.getUserKey();
			return true;
		} catch (IOException e) {
			return false;
		}
	}

	/**
	 * Encrypt the given {@link String} with the current {@link KeyGeneratorTool#getUserKey()} and
	 * encode the result in Base64.
	 * 
	 * @param text
	 * @return
	 * @throws CipherException
	 */
	public static String encrypt(String text) throws CipherException {
		Key key = null;
		try {
			key = KeyGeneratorTool.getUserKey();
		} catch (IOException e) {
			throw new CipherException("Cannot retrieve key, probably no one was created: " + e.getMessage());
		}
		return encrypt(text, key);
	}

	/**
	 * Encrypt the given {@link String} with the specified {@link Key} and encode the result in
	 * Base64.
	 * 
	 * @param text
	 * @param key
	 * @return
	 * @throws CipherException
	 */
	public static String encrypt(String text, Key key) throws CipherException {
		try {
			Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
			cipher.init(Cipher.ENCRYPT_MODE, key);

			byte[] outputBytes = cipher.doFinal(text.getBytes());

			String base64;
			if (text.length() < FORMER_MINLENGTH_TO_ENCODE) {
				// use new way to encode strings with less than 4 characters which was impossible
				// before 6.0.003
				base64 = DatatypeConverter.printBase64Binary(outputBytes) + FLAG_NEW_ENCODE;
			} else {
				// use old way for full backwards compatibility
				base64 = Base64.encodeBytes(outputBytes);
			}
			return base64;
		} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
			throw new CipherException("Failed to encrypt text: " + e.getMessage());
		}
	}

	/**
	 * Decrypt the given Base64 encoded and encrypted {@link String} with the current
	 * {@link KeyGeneratorTool#getUserKey()}.
	 * 
	 * @param text
	 * @return
	 * @throws CipherException
	 */
	public static String decrypt(String text) throws CipherException {
		Key key = null;
		try {
			key = KeyGeneratorTool.getUserKey();
		} catch (IOException e) {
			throw new CipherException("Cannot retrieve key, probably no one was created: " + e.getMessage());
		}
		return decrypt(text, key);
	}

	/**
	 * Decrypt the given Base64 encoded and encrypted {@link String} with the specified {@link Key}.
	 * 
	 * @param text
	 * @param key
	 * @return
	 * @throws CipherException
	 */
	public static String decrypt(String text, Key key) throws CipherException {
		try {
			byte[] encrypted;
			if (text.endsWith(FLAG_NEW_ENCODE)) {
				// encrypted strings with less than 4 characters which have been encrypted with
				// version 6.0.003 onwards use are encoded via DataTypeConverter instead
				encrypted = DatatypeConverter.parseBase64Binary(text.subSequence(0, text.length() - 1).toString());
			} else {
				encrypted = Base64.decode(text);
			}

			Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
			cipher.init(Cipher.DECRYPT_MODE, key);

			byte[] outputBytes = cipher.doFinal(encrypted);
			String ret = new String(outputBytes);
			return ret;
		} catch (NoSuchAlgorithmException | NoSuchPaddingException | IOException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | IllegalArgumentException e) {
			throw new CipherException("Failed to decrypt text: " + e.getMessage());
		}
	}

}
